﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace katOS
{
    /// <summary>
    /// Class that has the user programs instructions
    /// </summary>
    class UserPrograms
    {
        FileInUse a_file = new FileInUse();

    
        /// <summary>
        /// Set instruction: sets a new value on a register
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="reg_name">The name of the register to be setted</param>
        /// <param name="value">The value that will be asigned to the register</param>
        public void Set(Process new_process, String reg_name, String value)
        {
            if(!isRegister(reg_name))
            {
                whereToWrite(new_process, "Invalid register on Set");
                //Console.WriteLine("Invalid register on Set");
                End(new_process,-1+"",true);
            }
            else if(!isRegister(value))
            {
                new_process.setRegister(reg_name, int.Parse(value));
            }
            else
            {
                new_process.setRegister(reg_name, new_process.getRegister(value));
            }
        }

        /// <summary>
        /// Add instruction: adds the vlaue of 2 registers and stores the result on a register
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="destiny">The destiny register</param>
        /// <param name="source">The source 1 register</param>
        /// <param name="source2">The dource 2 register</param>
        public void Add(Process new_process, String destiny, String source, String source2)
        {
            if(!isRegister(destiny))
            {
                whereToWrite(new_process, "Invalid register on Add's destiny");
                //Console.WriteLine("Invalid register on Add's destiny");
                End(new_process, -1 + "",true);
            }
            else if(!isRegister(source))
            {
                whereToWrite(new_process, "Invalid register on Add's source");
                //Console.WriteLine("Invalid register on Add's source");
                End(new_process, -1 + "",true);
            }
            else if(!isRegister(source2))
            {
                whereToWrite(new_process, "Invalid register on Add's source2");
                //Console.WriteLine("Invalid register on Add's source2");
                End(new_process, -1 + "",true);
            }
            else
            {
                int a = new_process.getRegister(source);
                int b = new_process.getRegister(source2);
                int sum = a+b;
                new_process.setRegister(destiny,sum);
            }
        }

        /// <summary>
        /// Sub instruction: substract the vlaue of 2 registers and stores the result on a register
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="destiny">The destiny register</param>
        /// <param name="source">The source 1 register</param>
        /// <param name="source2">The source 2 register</param>
        public void Sub(Process new_process, String destiny, String source, String source2)
        {
            if (!isRegister(destiny))
            {
                whereToWrite(new_process, "Invalid register on Sub's destiny");
                //Console.WriteLine("Invalid register on Sub's destiny");
                End(new_process, -1 + "",true);
            }
            else if (!isRegister(source))
            {
                whereToWrite(new_process, "Invalid register on Sub's source");
                //Console.WriteLine("Invalid register on Sub's source");
                End(new_process, -1 + "",true);
            }
            else if (!isRegister(source2))
            {
                whereToWrite(new_process, "Invalid register on Sub's source2");
                //Console.WriteLine("Invalid register on Sub's source2");
                End(new_process, -1 + "",true);
            }
            else
            {
                int a = new_process.getRegister(source);
                int b = new_process.getRegister(source2);
                int sub = a - b;
                new_process.setRegister(destiny, sub);
            }
        }

        /// <summary>
        /// Mul instruction: multiplies the vlaue of 2 registers and stores the result on a register
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="destiny">The destiny register</param>
        /// <param name="source">The source 1 register</param>
        /// <param name="source2">The source 2 register</param>
        public void Mul(Process new_process, String destiny, String source, String source2)
        {
            if (!isRegister(destiny))
            {
                whereToWrite(new_process, "Invalid register on Mul's destiny");
                //Console.WriteLine("Invalid register on Mul's destiny");
                End(new_process, -1 + "",true);
            }
            else if (!isRegister(source))
            {
                whereToWrite(new_process, "Invalid register on Mul's source");
                //Console.WriteLine("Invalid register on Mul's source");
                End(new_process, -1 + "",true);
            }
            else if (!isRegister(source2))
            {
                whereToWrite(new_process, "Invalid register on Mul's source2");
                //Console.WriteLine("Invalid register on Mul's source2");
                End(new_process, -1 + "",true);
            }
            else
            {
                int a = new_process.getRegister(source);
                int b = new_process.getRegister(source2);
                int sum = a * b;
                new_process.setRegister(destiny, sum);
            }
        }

        /// <summary>
        /// Div instruction: divides the vlaue of 2 registers and stores the result 
        /// on a register and the remainder on another register
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="destiny">The destiny register</param>
        /// <param name="dividend">The register that has the dividend</param>
        /// <param name="divisor">The register that has the divisor</param>
        /// <param name="remainder">The register that will contain the remainder</param>
        public void Div(Process new_process, String destiny, String dividend, String divisor, String remainder)
        {
            if (!isRegister(destiny))
            {
                whereToWrite(new_process, "Invalid register on Div's destiny");
                //Console.WriteLine("Invalid register on Div's destiny");
                End(new_process, -1 + "",true);
            }
            else if (!isRegister(dividend))
            {
                whereToWrite(new_process, "Invalid register on Div's source");
                //Console.WriteLine("Invalid register on Div's source");
                End(new_process, -1 + "",true);
            }
            else if (!isRegister(divisor))
            {
                whereToWrite(new_process, "Invalid register on Div's source2");
                //Console.WriteLine("Invalid register on Div's source2");
                End(new_process, -1 + "",true);
            }
            else if (!isRegister(remainder))
            {
                whereToWrite(new_process, "Invalid register on Div's remainder");
                //Console.WriteLine("Invalid register on Div's remainder");
                End(new_process, -1 + "",true);
            }
            else
            {
                int div1 = new_process.getRegister(divisor);
                int div2 = new_process.getRegister(dividend);
                int res = div2 / div1;
                new_process.setRegister(destiny, res);
                int rem = div2 % div1;
                new_process.setRegister(remainder, rem);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="new_process"></param>
        /// <param name="reg1"></param>
        /// <param name="reg2"></param>
        /// <param name="reg3"></param>
        public void Random(Process new_process, String reg1, String reg2, String reg3)
        {
            if (!isRegister(reg1))
            {
                whereToWrite(new_process, "Invalid register on Random's inferior limit");
                End(new_process, -1 + "", true);
            }
            else if (!isRegister(reg2))
            {
                whereToWrite(new_process, "Invalid register on Random's superior limit");
                End(new_process, -1 + "", true);
            }
            else if (!isRegister(reg3))
            {
                whereToWrite(new_process, "Invalid register on Random's destiny");
                End(new_process, -1 + "", true);
            }
            else
            {
                int limit1 = new_process.getRegister(reg1);
                int limit2 = new_process.getRegister(reg2);
                Random randNum = new Random();
                int random_value = randNum.Next(limit1, limit2);
                new_process.setRegister(reg3,random_value);
            }
        }

        /// <summary>
        /// Echo instruction: makes echo of a string or the value of a register
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="a_string">The string to be showed</param>
        public void Echo(Process new_process, string a_string)
        {
            string to_print;
            if(isRegister(a_string))
            {
                to_print = new_process.getRegister(a_string)+" ";
            }
            else if (a_string.StartsWith("\"") && a_string.EndsWith("\"")&& a_string.CompareTo("\"\"")!=0)
            {
                to_print = a_string.Substring(1, a_string.Length - 2);
                while(to_print.Contains("\\n"))
                {
                    to_print=to_print.Replace("\\n", "\n"); //para que imprima enters
                }
            }
            else
            {
                to_print = "Invalid Echo";
                End(new_process, -2 + "",true);
            }
            whereToWrite(new_process,to_print);
            //Console.Write(to_print);        
        }

        /// <summary>
        /// getState instruction: retunrs the state of a process
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <returns>Returns the state of a process</returns>
        public string getState(Process new_process, string reg)
        {
            string state = "";
            int stateid = new_process.getState();
            if (isRegister(reg))
            {
                switch (stateid)
                {
                    case 1:
                        return "Process ID: " + new_process.getPID() + "New";
                    case 2:
                        return "Process ID: " + new_process.getPID() + "Ready";
                    case 3:
                        return "Process ID: " + new_process.getPID() + "Running";
                    case 4:
                        return "Process ID: " + new_process.getPID() + "Waiting";
                    case 5:
                        return "Process ID: " + new_process.getPID() + "Terminated";
                }
                new_process.setRegister(reg,new_process.getState());
            }
            else
            {
                state = "Wrong register on getState";
            }
            return state;
        }

        /// <summary>
        /// Jmp instruction: jumps to a tag
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="reg">Register name</param>
        /// <param name="tag">Tag</param>
        /// <param name="symbolsTable">Symbols table</param>
        /// <returns>Retunrs the ip to jump</returns>
        public int Jmp(Process new_process, string reg, string tag, string[,] symbolsTable)
        {
            int tempip = 0;
            if (isRegister(reg))
            {
                if (new_process.getRegister(reg) > 0)
                {
                    for (int i = 0; i < symbolsTable.Length; i++)
                    {
                        if (symbolsTable[i, 0] == tag)
                        {
                            tempip = int.Parse(symbolsTable[i, 1])-1;
                            new_process.setIP(tempip);
                            return tempip;
                        }
                    }
                }
            }
            else
            {
                whereToWrite(new_process, "Invalid register on Jmp");
                //Console.WriteLine("Invalid register on Jmp");
                End(new_process, -1 + "", true);
            }
            return 0;
        }

        /// <summary>
        /// End instruction:returns the end value of a process
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="reg">Register name</param>
        /// <param name="end">Boolean that represents if the end of the file has been reached</param>
        /// <returns>Returns the end value of the process</returns>
        public int End(Process new_process, string reg, bool end)
        {
            int value=0;
            if(!isRegister(reg))
            {
                value = int.Parse(reg);
                new_process.setProcessValue(value);
                new_process.setEnd(true);
                if (!new_process.getSync())
                {
                    new_process.getFile().Close();
                    /*if (new_process.getParent().Equals(-1))
                    {
                        Console.WriteLine("the file is being close for the parent process ROCK");
                        new_process.getFile().Close();
                    }
                    else
                    {
                        Console.WriteLine("the file will be closed for the parent process");
                    }*/
                }
            }
            else
            {
                value = new_process.getRegister(reg);
                new_process.setEnd(true);
                new_process.setProcessValue(value);
                if (!new_process.getSync())
                {
                    new_process.getFile().Close();
                }
            }
            return value;
        }

        /// <summary>
        /// OpenFile instruction: opens a file for the process thar made the request
        /// </summary>
        /// <param name="new_process">The process to wich the instruction belongs</param>
        /// <param name="filename">File name</param>
        /// <param name="reg">File handler</param>
        /// <returns>Returns the file handler</returns>
        public void OpenFile(Process new_process, string filename, string reg)
        {
            filename = filename.TrimEnd('"').TrimStart('"');
            if (isRegister(reg))
            {
                Console.WriteLine("Is trying to open a file");
                if (!FileManager.handlersList.ContainsValue(filename))
                {
                    Console.WriteLine("the file is being opened for the first time");
                    Console.WriteLine("The process ID is: "+new_process.getPID());
                    
                    int new_handler = FileManager.addHandler(filename);
                    new_process.setRegister(reg, new_handler);

                    Console.WriteLine("Reg value: " + new_process.getRegister(reg));

                    a_file.setHandler(new_handler);
                    a_file.setName(filename);
                    a_file.AddProcess(new_process.getPID());
                    a_file.setFileContent(filename);
                    Console.WriteLine("the open file reaches the dictionaries");
                    FileManager.addFileInUse(filename, a_file.getFileContent());
                    FileManager.setFileIP(filename, 0);
                    FileManager.addFilesList(filename, a_file.getProcessesList());

                    /* this is for debuggin only :D
                    foreach (KeyValuePair<int, string> pair in FileManager.handlersList)
                    {
                        Console.WriteLine("Handlers list "+pair);
                    }
                    foreach (KeyValuePair<string, int> pair in FileManager.getFileIP())
                    {
                        Console.WriteLine("IP List " + pair);
                    }
                    foreach (KeyValuePair<string, List<string>> pair in FileManager.getFileInUse())
                    {
                        Console.WriteLine("the content file list");
                        for (int i = 0; i < a_file.getFileContent().Count; i++)
                        {
                            Console.Write(pair.Key + " " + a_file.getFileContent().ElementAt(i)+"\n");
                        }
                    }
                    foreach (KeyValuePair<string, List<int>> pair in FileManager.getFileList())
                    {
                        Console.WriteLine("the processes list");
                        for (int i = 0; i < a_file.getProcessesList().Count; i++)
                        {
                            Console.Write(pair.Key + " " + a_file.getProcessesList().ElementAt(i) + "\n");
                        }
                    }*/
                    //Pasar proceso a waiting 5 ticks
                    new_process.setRegister(reg, new_handler);
                    //return new_handler; if is int
                }
                else
                {
                    Console.WriteLine("The file is open already");
                    Console.WriteLine("The process ID is: " + new_process.getPID());
                    //Pasar proceso a waiting indefinido hasta que se desocupe el archivo
                    a_file.AddProcess(new_process.getPID());

                    Console.WriteLine("the processes list for the file object");
                    for (int i = 0; i < a_file.getProcessesList().Count; i++)
                    {
                        Console.Write("process: "+a_file.getProcessesList().ElementAt(i) + "\n");
                    }

                    FileManager.getFileList().Remove(filename);
                    FileManager.addFilesList(filename, a_file.getProcessesList());

                    Console.WriteLine("has passed the process list asignation");
                    foreach (KeyValuePair<string, List<int>> pair in FileManager.getFileList())
                    {
                        Console.WriteLine("the processes list");
                        for (int i = 0; i < a_file.getProcessesList().Count; i++)
                        {
                            Console.Write(pair.Key + " " + a_file.getProcessesList().ElementAt(i) + "\n");
                        }
                    }

                    int reg_value;
                    foreach (KeyValuePair<int, string> pair in FileManager.handlersList)
                    {
                        if (filename.Equals(pair.Value))
                        {
                            reg_value = pair.Key;
                        }
                    }
 
                    reg_value = FindKeyByValue(FileManager.handlersList, filename);
                    new_process.setRegister(reg, reg_value);
                    //return reg_value; if is int
                }
            }
            else
            {
                Console.WriteLine("Not a register");
                End(new_process, -1 + "", true);
            }
            //return 0; if is int
        }

        public void CloseFile(Process new_process, string reg)
        {
            if (isRegister(reg))
            {
                int reg_value = new_process.getRegister(reg);
                if (FileManager.handlersList.ContainsKey(reg_value))
                {
                    Console.WriteLine("the handler sent exists in the handlers dictionary");
                    string filename = FileManager.handlersList[reg_value];
                    List<int> temp_process_list = new List<int>();

                    //temp_process_list = a_file.getProcessesList();
                    temp_process_list = FileManager.getFileList()[filename];
                    Console.WriteLine("The list has "+temp_process_list.Count+" processes");
                    if (temp_process_list.Count == 1 || temp_process_list.Count == 0)
                    //if (a_file.getProcessesList().Count == 0)
                    {
                        //add to logs and statitistics
                        //remove from every dictionary
                        FileManager.getFileList().Remove(FileManager.getHandlers()[reg_value]);
                        FileManager.getFileInUse().Remove(FileManager.getHandlers()[reg_value]);
                        FileManager.getFileIP().Remove(FileManager.getHandlers()[reg_value]);
                        FileManager.handlersList.Remove(reg_value);
                        Console.WriteLine("the file is close for good ");
                    }
                    else
                    {
                        int temp_pid = new_process.getPID();
                        a_file.RemoveProcess(temp_pid);
                        //temp_process_list = a_file.getProcessesList();
                        //To update the processes list for the file      
                        FileManager.getFileList().Remove(FileManager.getHandlers()[reg_value]);
                        FileManager.addFilesList(filename, a_file.getProcessesList());
                        Console.WriteLine("Now the list has " + temp_process_list.Count + " processes");
                    }
                }
                else
                {
                    Console.WriteLine("Not such handler exists on closefile");
                    End(new_process, -1 + "", true);
                }
            }
            else
            {
                Console.WriteLine("Not a register");
                End(new_process, -1 + "", true);
            }
        }

        public void ReadLine(Process new_process, string reg1, string reg2)
        {
            int reg2_value;
            //Console.WriteLine("Reg 1 value: " + new_process.getRegister(reg1));
            //Console.WriteLine("Reg 2 value: " + new_process.getRegister(reg2));
            if (isRegister(reg1) && isRegister(reg2))
            {
                if (FileManager.getHandlers().ContainsKey(new_process.getRegister(reg1)))
                {
                    //Console.WriteLine("the readline of the file is beginning");
                    string filename = FileManager.getHandlers()[new_process.getRegister(reg1)];

                    if (FileManager.getFileList()[filename].Count == 1 || FileManager.getFileList()[filename].Count == 0)
                    {
                        //Console.WriteLine("The file name is: "+filename);

                        List<string> temp_file_content = FileManager.getFileInUse()[filename];

                        /*Console.WriteLine("the file content is");
                        for (int i = 0; i < temp_file_content.Count; i++)
                        {
                            Console.Write(temp_file_content.ElementAt(i)+ "\n");
                        }*/

                        int temp_file_ip = FileManager.getFileIP()[filename];
                        string a_line = temp_file_content.ElementAt(temp_file_ip);
                        temp_file_ip++;
                        FileManager.getFileIP().Remove(filename);
                        FileManager.setFileIP(filename, temp_file_ip);

                        new_process.setRegister(reg2, a_line.Length);
                        reg2_value = new_process.getRegister(reg2);
                        whereToWrite(new_process, a_line);
                        //Console.WriteLine(a_line);
                        //Console.WriteLine("After Reg 1 value: " + new_process.getRegister(reg1));
                        //Console.WriteLine("After Reg 2 value: " + new_process.getRegister(reg2));
                    }
                    else
                    { 
                        //Send to indefinite wait
                        whereToWrite(new_process, "the readline can't be performed until the file is not in use anymore");
                        //Console.WriteLine("the readline can't be performed until the file is not in use anymore");
                    }
                }
                else
                {
                    whereToWrite(new_process, "Not such handler exists on readline");
                    if (!new_process.getSync())
                    {
                        new_process.getFile().Close();
                    }
                    End(new_process, -1 + "", true);
                    // Console.WriteLine("Not such handler exists on readline");
                }

            }
        }

        

        public void Fork(Process new_process, string instruction, string reg)
        {
            string temp = instruction.Substring(0, instruction.Length);
            int sons = 0;
            if (!new_process.getSync())
            {
                sons++;
                string new_outfile = "son_"+sons+new_process.getOutFile();
                temp = temp + " > "+new_outfile;
            }

            if (isRegister(reg))
            {
                Console.WriteLine("gets to the register validation on fork");
                if (Shell.IsSystemProgram(temp))
                {
                    
                    Console.WriteLine("is going to excecute the system program");
                    Console.WriteLine("just reach the is system program on fork");

                    Console.WriteLine("just GOT OUT of the is system program on fork");
                }
                else
                {
                    //Console.WriteLine("The PID of the process that made fork is: " + new_process.getPID());
                    //Console.WriteLine("The parent process of the process that made fork is: " + new_process.getParent());
                    int last = Scheduler.queue_list.ElementAt(Scheduler.start_queue).getQueue().Count;
                    

                    //This is for debuggind only
                    /*for (int i = 0; i < last; i++)
                    {
                        Console.WriteLine("Process PID: " + Scheduler.queue_list.ElementAt(Scheduler.start_queue).getQueue().ElementAt(i).getPID() + " File: " + Scheduler.queue_list.ElementAt(Scheduler.start_queue).getQueue().ElementAt(i).getFileName() + " Parent: " + Scheduler.queue_list.ElementAt(Scheduler.start_queue).getQueue().ElementAt(i).getParent());
                    }*/

                    int reg_value = Scheduler.queue_list.ElementAt(Scheduler.start_queue).getLastProcess().getPID();
                    new_process.setRegister(reg, reg_value);
                }
            }
            else
            {
                whereToWrite(new_process, "Wrong parameters for fork");
                End(new_process, -1+"", true);//terminated //error en el fork
            }
        }

        public void Wait(Process new_process, string reg)
        {
            if (isRegister(reg))
            {
                try
                {
                    int temp = new_process.getRegister(reg);

                    bool exists = false;
                    for (int i = 0; i < Scheduler.semaphores_list.Count; i++)
                    {
                        if (Scheduler.semaphores_list.ElementAt(i).getID() == temp)
                        {
                            exists = true;
                        }
                    }
                    Semaphore semaphore = null;

                    if (!exists)
                    {
                        semaphore = new Semaphore(temp);
                        Scheduler.semaphores_list.AddLast(semaphore);
                    }
                    else
                    {
                        semaphore = null;
                        for (int i = 0; i < Scheduler.semaphores_list.Count; i++)
                        {
                            if (Scheduler.semaphores_list.ElementAt(i).getID() == temp)
                            {
                                semaphore = Scheduler.semaphores_list.ElementAt(i);
                            }
                        }
                    }

                    if (semaphore.Wait())
                    {
                        semaphore.addToQueue(new_process);
                        Scheduler.queue_list.ElementAt(new_process.getQueue()).Remove(new_process);
                    }
                }
                catch (Exception)
                {
                    End(new_process, -6 + "", true);
                }
            }
            else
            {
                End(new_process, -7 + "", true);
                whereToWrite(new_process, "Wrong register on semaphore Wait");
            }
        }

        public void Signal(Process new_process, string reg)
        {
            if (isRegister(reg))
            {
                try
                {
                    int temp = new_process.getRegister(reg);

                    Semaphore semaphore = null;
                    for (int i = 0; i < Scheduler.semaphores_list.Count; i++)
                    {
                        if (Scheduler.semaphores_list.ElementAt(i).getID() == temp)
                        {
                            semaphore = Scheduler.semaphores_list.ElementAt(i);
                        }
                    }
                    if (semaphore == null)
                    {
                        End(new_process, -6 + "", true);
                    }
                    else
                    {
                        if (semaphore.Signal())
                        {
                            Process process = semaphore.outOfQueue();
                            Scheduler.queue_list.ElementAt(process.getQueue()).addProcess(new_process);
                        }
                    }
                }
                catch (Exception)
                {
                    End(new_process, -6 + "", true);
                }
            }
            else
            {
                End(new_process, -7 + "", true);
                whereToWrite(new_process, "Wrong register on semaphore Signal");
            }
        }

        public void Sleep(Process new_process)
        {
            new_process.setState(4);
        }

        private int FindKeyByValue(Dictionary<int, string> dictionary, string value)
        {
            int key_value = -1;
            if (dictionary == null)
            {
                return key_value;
            }
            else
            {
                foreach (KeyValuePair<int, string> pair in dictionary)
                {
                    if (value.Equals(pair.Value))
                    {
                        key_value = pair.Key;
                    }
                }
                return key_value;
            }
        }

        private void whereToWrite(Process new_process, string to_print)
        {
            if (new_process.getSync())
            {
                Console.Write(to_print);
            }
            else
            {
                new_process.getFile().Write(to_print);
            }
        }

        /// <summary>
        /// Method that checks if the string is a register name
        /// </summary>
        /// <param name="pos_reg">The register name to analize</param>
        /// <returns>Returns true if the string is a register name</returns>
        private bool isRegister(string pos_reg)
        {
            pos_reg = pos_reg.ToLower();
            bool reg = false;
            if (pos_reg.Equals("r0") || pos_reg.Equals("r1") || pos_reg.Equals("r2") || pos_reg.Equals("r3") || pos_reg.Equals("r4") || pos_reg.Equals("r5") || pos_reg.Equals("r6") || pos_reg.Equals("r7"))
            {
                reg = true;
            }
            return reg;
        }
    }
}
